<template>
  <div class="color-select">
    <div class="saturation-value" ref="saturation_value" @mousedown="mousedownSV">
      <div :style="'background-color: hsl(' + hue + ', 100%, 50%);'">
        <div class="point" :style="pointStyle"></div>
      </div>
      <div class="saturation-value-2"></div>
      <div class="saturation-value-3"></div>
    </div>
    <div class="color-select-middle">
      <div style="flex: auto">
        <div class="hue-slider" ref="hue_slider" @mousedown="mousedownHue">
          <div class="slider" :style="hueSliderStyle"></div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "MyColorSelect",
  props: {
    color: {
      type: String,
      default: "",
    },
    alphaValue: {
      type: Number,
      default: 100,
    },
  },
  data() {
    return {
      pointStyle: "top: 25%; left: 80%;",
      hueSliderStyle: "left: 0;",
      hue: 0,
      saturation: 1,
      value: 1,
      red: 255,
      green: 0,
      blue: 0,
      alpha: 1,
    };
  },
  watch: {
    color: {
      handler(val) {
        this.handParse(val);
      },
    },
    alphaValue: {
      handler(val) {
        this.$emit(
          "update:color",
          this.rgba2hex(this.red, this.green, this.blue, this.matchHexA())
        );
      },
    },
  },
  mounted() {
    this.handParse(this.color);
  },
  methods: {
    handParse(color) {
      if ((this.parseColor(color) ?? "") !== "") {
        const { r, g, b, a } = this.parseColor(color);
        this.red = r;
        this.green = g;
        this.blue = b;
        this.alpha = a;
        this.updateColor();
      }
    },
    updateColor() {
      let { h, s, v } = this.rgb2hsv(this.red, this.green, this.blue);
      this.hue = h;
      this.saturation = s;
      this.value = v;

      // 移动背景板圆圈
      this.pointStyle = `top: ${100 - v * 100}%;left: ${s * 100}%;`;
      // 移动色调滑块
      this.hueSliderStyle = `left: ${(this.hue / 360) * 100}%;`;
    },
    rgb2hsv(r, g, b) {
      let r1 = r / 255;
      let g1 = g / 255;
      let b1 = b / 255;
      let cmax = Math.max(r1, g1, b1);
      let cmin = Math.min(r1, g1, b1);
      let d = cmax - cmin;
      let h, s, v;
      if (d === 0) {
        h = 0;
      } else if (cmax === r1) {
        h = ((60 * (g1 - b1)) / d + 360) % 360;
      } else if (cmax === g1) {
        h = 60 * ((b1 - r1) / d + 2);
      } else if (cmax === b1) {
        h = 60 * ((r1 - g1) / d + 4);
      }
      if (cmax === 0) {
        s = 0;
      } else {
        s = d / cmax;
      }
      v = cmax;
      h = Math.floor(h + 0.5);
      s = Math.floor(s * 100 + 0.5) / 100;
      v = Math.floor(v * 100 + 0.5) / 100;
      return { h, s, v };
    },
    // 色调
    handleChangeHue(e) {
      let w = this.$refs.hue_slider.clientWidth;
      let x =
        e.pageX - this.$refs.saturation_value.getBoundingClientRect().left;
      x = x < w && x > 0 ? x : x > w ? w : 0;
      // 计算色调
      this.hue = Math.floor((x / w) * 360 + 0.5);
      // hsv转化为rgb
      let { r, g, b } = this.hsv2rgb(this.hue, this.saturation, this.value);
      this.red = r;
      this.green = g;
      this.blue = b;
      // 移动滑块
      this.hueSliderStyle = `left: ${x >= w - 6 ? w - 6 : x}px;`;
      this.$emit("update:color", this.rgba2hex(r, g, b, this.matchHexA()));
    },

    mousedownHue(e) {
      this.handleChangeHue(e);
      window.addEventListener("mousemove", this.handleChangeHue);
      window.addEventListener("mouseup", this.mouseupHue);
    },

    mouseupHue(e) {
      window.removeEventListener("mousemove", this.handleChangeHue);
      window.removeEventListener("mouseup", this.mouseupHue);
    },
    // 饱和度和亮度
    handleChangeSV(e) {
      let w = this.$refs.saturation_value.clientWidth;
      let h = this.$refs.saturation_value.clientHeight;
      let x =
        e.pageX - this.$refs.saturation_value.getBoundingClientRect().left;
      let y = e.pageY - this.$refs.saturation_value.getBoundingClientRect().top;
      x = x < w && x > 0 ? x : x > w ? w : 0;
      y = y < h && y > 0 ? y : y > h ? h : 0;
      // 计算饱和度和亮度
      this.saturation = Math.floor((x / w) * 100 + 0.5) / 100;
      this.value = Math.floor((1 - y / h) * 100 + 0.5) / 100;
      // hsv转化为rgb
      let { r, g, b } = this.hsv2rgb(this.hue, this.saturation, this.value);
      this.red = r;
      this.green = g;
      this.blue = b;
      // 移动背景板圆圈
      this.pointStyle = `top: ${y}px;left: ${x}px;`;
      this.$emit("update:color", this.rgba2hex(r, g, b, this.matchHexA()));
    },
    matchHexA() {
      let percentage = this.alphaValue * 0.01;
      return percentage;
    },
    mousedownSV(e) {
      // 鼠标按下计算饱和度和亮度并添加事件
      this.handleChangeSV(e);
      // 添加整个页面的鼠标事件
      window.addEventListener("mousemove", this.handleChangeSV);
      window.addEventListener("mouseup", this.mouseupSV);
    },
    mouseupSV(e) {
      // 鼠标松开后移除事件
      window.removeEventListener("mousemove", this.handleChangeSV);
      window.removeEventListener("mouseup", this.mouseupSV);
    },
    parseColor(color) {
      if (color) {
        let r, g, b, a;
        if (typeof color === "string") {
          if (
            /^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8}|[0-9a-fA-F]{3}|[0-9a-fA-F]{4})$/.test(
              color
            )
          ) {
            return this.hex2rgba(color);
          }
        } else {
          r = color.r > 255 ? 255 : color.r < 0 ? 0 : color.r;
          g = color.g > 255 ? 255 : color.g < 0 ? 0 : color.g;
          b = color.b > 255 ? 255 : color.b < 0 ? 0 : color.b;
          a = color.a > 1 ? 1 : color.a < 0 ? 0 : color.a;
          return { r, g, b, a };
        }
      } else {
        return null;
      }
    },
    hsv2rgb(h, s, v) {
      h === 360 && (h = 0);
      let i = Math.floor(h / 60) % 6;
      let f = h / 60 - i;
      let p = v * (1 - s);
      let q = v * (1 - s * f);
      let t = v * (1 - s * (1 - f));
      let r, g, b;
      if (i === 0) {
        r = v;
        g = t;
        b = p;
      } else if (i === 1) {
        r = q;
        g = v;
        b = p;
      } else if (i === 2) {
        r = p;
        g = v;
        b = t;
      } else if (i === 3) {
        r = p;
        g = q;
        b = v;
      } else if (i === 4) {
        r = t;
        g = p;
        b = v;
      } else if (i === 5) {
        r = v;
        g = p;
        b = q;
      }
      r = Math.floor(r * 255 + 0.5);
      g = Math.floor(g * 255 + 0.5);
      b = Math.floor(b * 255 + 0.5);
      return { r, g, b };
    },

    rgba2hex(r, g, b, a = 1) {
      r = parseInt(r);
      let r1 =
        r.toString(16).length !== 2 ? "0" + r.toString(16) : r.toString(16);
      g = parseInt(g);
      let g1 =
        g.toString(16).length !== 2 ? "0" + g.toString(16) : g.toString(16);
      b = parseInt(b);
      let b1 =
        b.toString(16).length !== 2 ? "0" + b.toString(16) : b.toString(16);
      a = parseFloat(a);
      let a1 = "";
      if (a !== 1) {
        let temp = Math.floor(256 * a);
        a1 =
          temp.toString(16).length !== 2
            ? "0" + temp.toString(16)
            : temp.toString(16);
      }
      return `#${r1}${g1}${b1}${a1}`.toUpperCase();
    },

    hex2rgba(s) {
      if (/^#?[0-9a-fA-F]{3}$/.test(s)) {
        let b = s.substring(s.length - 1, s.length);
        let g = s.substring(s.length - 2, s.length - 1);
        let r = s.substring(s.length - 3, s.length - 2);
        return hex2rgba(`${r + r}${g + g}${b + b}`);
      }
      if (/^#?[0-9a-fA-F]{4}$/.test(s)) {
        let a = s.substring(s.length - 1, s.length);
        let b = s.substring(s.length - 2, s.length - 1);
        let g = s.substring(s.length - 3, s.length - 2);
        let r = s.substring(s.length - 4, s.length - 3);
        return hex2rgba(`${r + r}${g + g}${b + b}${a + a}`);
      }
      if (/^#?[0-9a-fA-F]{6}$/.test(s)) {
        let b = parseInt("0x" + s.substring(s.length - 2, s.length));
        let g = parseInt("0x" + s.substring(s.length - 4, s.length - 2));
        let r = parseInt("0x" + s.substring(s.length - 6, s.length - 4));
        return { r, g, b, a: 1 };
      }
      if (/^#?[0-9a-fA-F]{8}$/.test(s)) {
        let a = parseInt("0x" + s.substring(s.length - 2, s.length));
        a = a / 255;
        let b = parseInt("0x" + s.substring(s.length - 4, s.length - 2));
        let g = parseInt("0x" + s.substring(s.length - 6, s.length - 4));
        let r = parseInt("0x" + s.substring(s.length - 8, s.length - 6));
        return { r, g, b, a };
      }
    },
  },
};
</script>

<style scoped>
.color-select {
  position: relative;
  user-select: none;
  width: 100%;
  padding: 12px 0 0 0;
}

/* 饱和度和亮度 */
.saturation-value {
  cursor: pointer;
  width: 100%;
  height: 148px;
  position: relative;
  margin-bottom: 12px;
  box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
}

.saturation-value>div {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

/* 圆圈 */
.point {
  box-sizing: border-box;
  width: 6px;
  height: 6px;
  background-color: transparent;
  border: 2px solid #ccc;
  border-radius: 50%;
  transform: translate(-50%, -50%);
  position: absolute;
  z-index: 9;
}

.saturation-value-2 {
  background: linear-gradient(to right, white, #ffffff00);
}

.saturation-value-3 {
  background: linear-gradient(to top, black, #ffffff00);
}

/* 色调滑块条 */
.hue-slider {
  position: relative;
  height: 10px;
  background: linear-gradient(90deg,
      red 0,
      #ff0 17%,
      #0f0 33%,
      #0ff 50%,
      #00f 67%,
      #f0f 83%,
      red);
  box-shadow: 1px 1px 1px rgba(0, 0, 0, 0.1);
}

/* 滑块 */
.slider {
  position: absolute;
  box-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
  box-sizing: border-box;
  width: 6px;
  height: 100%;
  background-color: #fff;
}
</style>
